#include "ThreadManager.h"
#include <assert.h>
#include "ThreadPool.h"
#include "../Core/CoreGlobals.h"
#include "../Core/MainThread.h"
#include "../Game/GameThread.h"
#include "../Render/RenderThread.h"
#include "../Render/RHIThread.h"
#include "../Utils/LogUtil.h"
#include "AsyncTask.h"
#include "TaskGraph.h"

uint32 GMainThreadId;
uint32 GGameThreadId;
uint32 GRenderThreadId;
uint32 GRHIThreadId;
ThreadPool* GThreadPool;

bool ThreadManager::mIsInitialized = false;

void ThreadManager::Start()
{
	// use to make sure all the thread are start by the same time
	mTimeToExit.store(false, std::memory_order_relaxed);
	
	GMainThreadId = ::GetCurrentThreadId();

	std::shared_ptr<std::promise<void>> startPromise = std::make_shared<std::promise<void>>();
	std::shared_future<void> startFuture(startPromise->get_future());

	// const uint32 threadNums = min(max(std::thread::hardware_concurrency() - 2, 2), 8);
	const uint32 threadNums = glm::clamp<int32>(static_cast<int32>(std::thread::hardware_concurrency() - 2), 2, 8);
	LOGI(L"hardware_concurrency %d", threadNums);
	GThreadPool = new ThreadPool(startFuture);
	GThreadPool->Create(threadNums == 0 ? 2 : threadNums, ThreadPriority::TPri_Normal);

	mWaitRenderInit = CreateEvent(nullptr, false, 0, nullptr);
	assert(mWaitRenderInit != nullptr);
	ResetEvent(mWaitRenderInit);

	mWaitGameInit = CreateEvent(nullptr, false, 0, nullptr);
	assert(mWaitGameInit != nullptr);
	ResetEvent(mWaitGameInit);

	mWaitRenderExit = CreateEvent(nullptr, false, 0, nullptr);
	assert(mWaitRenderExit != nullptr);
	ResetEvent(mWaitRenderExit);

	mWaitGameExit = CreateEvent(nullptr, false, 0, nullptr);
	assert(mWaitGameExit != nullptr);
	ResetEvent(mWaitGameExit);

	GameThread* gameThread = new GameThread(startFuture, this);
	GGameThreadId = gameThread->Setup(L"GameThread", ThreadPriority::TPri_Highest);
	mTaskThreads.insert({ GGameThreadId, gameThread });

	RenderThread* renderThread = new RenderThread(startFuture, this);
	GRenderThreadId = renderThread->Setup(L"RenderThread", ThreadPriority::TPri_Highest);
	mTaskThreads.insert({ GRenderThreadId, renderThread });

#if 0
	RHIThread* rhiThread = new RHIThread;
	GRHIThreadId = rhiThread->Setup(L"RHIThread", ThreadPriority::TPri_Highest);
	mTaskThreads.insert({ GRHIThreadId, rhiThread });
#endif

	MainThread* mainThread = new MainThread(startPromise);
	mTaskThreads.insert({ GMainThreadId, mainThread });
	mainThread->Setup(L"MainThread", ThreadPriority::TPri_Highest);
}

void ThreadManager::Stop()
{
	mTimeToExit.store(true, std::memory_order_release);

	GThreadPool->Destroy();
	for (auto& thread : mTaskThreads)
	{
		thread.second->Kill();
	}
}

bool ThreadManager::IsTimeToExit() const
{
	return mTimeToExit.load(std::memory_order_relaxed);
}

void ThreadManager::QueueTask(BaseGraphTask* task, uint32 threadIdToExecuteOn)
{
	// assert(mTaskThreads.find(threadIdToExecuteOn) != mTaskThreads.end());
	// mTaskThreads.find(threadIdToExecuteOn)->second->EnqueueTask(task);
	mTaskThreads[threadIdToExecuteOn]->EnqueueTask(task);
}

void ThreadManager::AddThread(uint32 threadId, RunnableThread* runnablethread)
{
	std::lock_guard<std::mutex> lk(mThreadsCritical);
	if (mThreads.find(threadId) == mThreads.end())
	{
		mThreads.insert({ threadId, runnablethread });
	}
}

void ThreadManager::RemoveThread(RunnableThread* runnablethread)
{
	std::lock_guard<std::mutex> lk(mThreadsCritical);
	uint32 threadId = 0;
	for (auto& threadIt : mThreads)
	{
		if (threadIt.second == runnablethread)
		{
			threadId = threadIt.first;
			break;
		}
	}
	assert(threadId != 0);
}

const std::wstring& ThreadManager::GetThreadName(uint32 threadId)
{
	static std::wstring noThreadName;
	std::lock_guard<std::mutex> lk(mThreadsCritical);
	if (mThreads.find(threadId) != mThreads.end())
	{
		return mThreads.find(threadId)->second->GetThreadName();
	}
	return noThreadName;
}

bool ThreadManager::IsInitialized()
{
	return mIsInitialized;
}

void ThreadManager::ForEachThread(std::function<void(uint32, RunnableThread*)> func)
{
	std::lock_guard<std::mutex> lk(mThreadsCritical);
	for (auto& threadIt : mThreads)
	{
		func(threadIt.first, threadIt.second);
	}
}

ThreadManager& ThreadManager::Get()
{
	static ThreadManager Singleton;
	ThreadManager::mIsInitialized = true;
	return Singleton;
}
